﻿using System.Collections.Generic;
using MODELENGLib;

namespace Visio.ForwardEngineer.DataModel
{
    /// <summary>
    /// This static class hides the complexity of loading the data model
    /// from Visio, it exposes methods to transform the Visio model, as
    /// exposed from ModelEng, to the internal data model
    ///     
    /// MODELENG is very incomplete and does not provide access to all of 
    /// the properties of the data model. Thus, there are a lot of limitations
    /// in the creation of a data model.
    /// </summary>
    static class ModelTransformer
    {
        /// <summary>
        /// This is the main object used to query the data model
        /// </summary>
        static VisioModelingEngine visioModelingEngine = new VisioModelingEngine();

        /// <summary>
        /// Returns the names of the models available, it is used by the user interface
        /// to let the user choose which data model to generate, in case more than one
        /// is loaded in memory
        /// </summary>
        /// <returns></returns>
        static public string[] GetModels()
        {
            List<string> result = new List<string>();

            IEnumIVMEModels modelsEnumerator = visioModelingEngine.Models;
            for (IVMEModel model = modelsEnumerator.Next(); model != null; model = modelsEnumerator.Next())
            {
                result.Add(model.Filename);
            }
            return result.ToArray();
        }

        /// <summary>
        /// Performs the validation of a model
        /// </summary>
        /// <param name="modelFileName"></param>
        static public void ValidateModelStructure(string modelFileName)
        {
            //  Searches for the right model among the ones available in
            //  the visioModelingEngine
            IEnumIVMEModels modelsEnumerator = visioModelingEngine.Models;
            IVMEModel model = modelsEnumerator.Next();
            while (model.Filename != modelFileName)
            {
                model = modelsEnumerator.Next();
            }
            model.CheckModel();
        }


        /// <summary>
        /// Transforms the Visio Data Model into our data model, so that
        /// we can later create the database
        /// </summary>
        /// <param name="modelFileName"></param>
        /// <returns></returns>
        static public ModelStructure GetModelStructure(string modelFileName, List<string> specifiedTables = null)
        {
            ModelStructure result = new ModelStructure();

            //  Searches for the right model among the ones available in
            //  the visioModelingEngine
            IEnumIVMEModels modelsEnumerator = visioModelingEngine.Models;
            IVMEModel model = modelsEnumerator.Next();
            while (model.Filename != modelFileName)
            {
                model = modelsEnumerator.Next();
            }

            //  This enumerator let us browse the elements in the model
            //  to search for the ones we are interested in
            IEnumIVMEModelElements elementEnumerator = model.Elements;

            //  Iterates over all the entities in the model, searching
            //  for tables.
            //  Each entity is a table, we gather column and index information from here
            for (IVMEModelElement element = elementEnumerator.Next(); element != null; element = elementEnumerator.Next())
            {
                if (element.Type == VMEModelElementKind.eVMEKindEREntity)
                {
                    IVMEEntity entity = element as IVMEEntity;

                    TableStructure newTable = new TableStructure();
                    newTable.QualifiedName = entity.PhysicalName;

                    // when there are specified tables
                    if (specifiedTables != null && !specifiedTables.Contains(newTable.QualifiedName.Trim()))
                    {
                        continue;
                    }

                    //  Here we search for the columns of the table
                    IEnumIVMEAttributes columnEnumerator = entity.Attributes;
                    for (IVMEAttribute columnDefinition = columnEnumerator.Next(); columnDefinition != null; columnDefinition = columnEnumerator.Next())
                    {
                        ColumnStructure newColumn = new ColumnStructure();
                        newColumn.Name = columnDefinition.PhysicalName;
                        newColumn.DataType = columnDefinition.DataType.PhysicalName;
                        newColumn.isNullable = columnDefinition.AllowNulls;
                        newTable.Columns.Add(newColumn);
                    }

                    //  Now we search for indexes in the structure
                    IEnumIVMEEntityAnnotations indexesEnumerator = entity.EntityAnnotations;
                    for (IVMEEntityAnnotation indexDefinition = indexesEnumerator.Next(); indexDefinition != null; indexDefinition = indexesEnumerator.Next())
                    {
                        IndexStructure newIndex = new IndexStructure();
                        newIndex.Name = indexDefinition.PhysicalName;
                        newIndex.isPrimaryKey = indexDefinition.kind == VMEEREntityAnnotationKind.eVMEEREntityAnnotationPrimary;
                        newIndex.isClustered = newIndex.isPrimaryKey;
                        newIndex.isUnique = indexDefinition.kind == VMEEREntityAnnotationKind.eVMEEREntityAnnotationAlternate;

                        IEnumIVMEAttributes indexColumnEnumerator = indexDefinition.Attributes;
                        for (IVMEAttribute indexColumn = indexColumnEnumerator.Next(); indexColumn != null; indexColumn = indexColumnEnumerator.Next())
                        {
                            newIndex.Columns.Add(newTable.Columns[indexColumn.PhysicalName]);
                        }
                        newTable.Indexes.Add(newIndex);
                    }
                    result.Tables.Add(newTable);
                }
            }

            //  Iterates over all the entities in the model, searching
            //  for relationships.
            //
            //  for each relationship, we need to find the related tables
            //  and all the interesting properties of the relationship
            elementEnumerator = model.Elements;
            for (IVMEModelElement element = elementEnumerator.Next(); element != null; element = elementEnumerator.Next())
            {
                if (element.Type == VMEModelElementKind.eVMEKindERRelationship)
                {
                    IVMEBinaryRelationship relationship = element as IVMEBinaryRelationship;

                    if (relationship.FirstEntity != null && relationship.SecondEntity != null)
                    {
                        // when there are specified tables
                        if (specifiedTables != null && !(specifiedTables.Contains(relationship.FirstEntity.PhysicalName.Trim())
                            || specifiedTables.Contains(relationship.SecondEntity.PhysicalName.Trim())))
                        {
                            continue;
                        }

                        ForeignKeyStructure newRelationship = new ForeignKeyStructure();
                        newRelationship.Name = relationship.PhysicalName;

                        TableStructure primaryTable = result.Tables[relationship.FirstEntity.PhysicalName];
                        TableStructure referencedTable = result.Tables[relationship.SecondEntity.PhysicalName];

                        newRelationship.Name = relationship.PhysicalName;
                        newRelationship.ReferencedTable = referencedTable;

                        //  Sets up the delete and update rules
                        switch (relationship.DeleteRule)
                        {
                            case VMEERRIRuleKind.eVMERIRuleDoNotEnforce:
                                newRelationship.DeleteAction = ForeignKeyStructure.ForeignKeyAction.NoAction;
                                break;
                            case VMEERRIRuleKind.eVMERIRuleNoAction:
                                newRelationship.DeleteAction = ForeignKeyStructure.ForeignKeyAction.NoAction;
                                break;
                            case VMEERRIRuleKind.eVMERIRuleCascade:
                                newRelationship.DeleteAction = ForeignKeyStructure.ForeignKeyAction.Cascade;
                                break;
                            case VMEERRIRuleKind.eVMERIRuleSetDefault:
                                newRelationship.DeleteAction = ForeignKeyStructure.ForeignKeyAction.SetDefault;
                                break;
                            case VMEERRIRuleKind.eVMERIRuleSetNull:
                                newRelationship.DeleteAction = ForeignKeyStructure.ForeignKeyAction.SetNull;
                                break;
                        }
                        switch (relationship.UpdateRule)
                        {
                            case VMEERRIRuleKind.eVMERIRuleDoNotEnforce:
                                newRelationship.UpdateAction = ForeignKeyStructure.ForeignKeyAction.NoAction;
                                break;
                            case VMEERRIRuleKind.eVMERIRuleNoAction:
                                newRelationship.UpdateAction = ForeignKeyStructure.ForeignKeyAction.NoAction;
                                break;
                            case VMEERRIRuleKind.eVMERIRuleCascade:
                                newRelationship.UpdateAction = ForeignKeyStructure.ForeignKeyAction.Cascade;
                                break;
                            case VMEERRIRuleKind.eVMERIRuleSetDefault:
                                newRelationship.UpdateAction = ForeignKeyStructure.ForeignKeyAction.SetDefault;
                                break;
                            case VMEERRIRuleKind.eVMERIRuleSetNull:
                                newRelationship.UpdateAction = ForeignKeyStructure.ForeignKeyAction.SetNull;
                                break;
                        }

                        //  Now we need to add the columns that are used to mantain the
                        //  relationship. We need two cursors, one for the primary
                        //  table and the other for the referenced one
                        IEnumIVMEAttributes primaryColumnEnumerator = relationship.FirstAttributes;
                        IVMEAttribute primaryColumn = primaryColumnEnumerator.Next();

                        IEnumIVMEAttributes referencedColumnEnumerator = relationship.SecondAttributes;
                        IVMEAttribute referencedColumn = referencedColumnEnumerator.Next();

                        while (primaryColumn != null)
                        {
                            newRelationship.SourceColumns.Add(primaryTable.Columns[primaryColumn.PhysicalName]);
                            newRelationship.ReferencedColumns.Add(referencedTable.Columns[referencedColumn.PhysicalName]);
                            primaryColumn = primaryColumnEnumerator.Next();
                            referencedColumn = referencedColumnEnumerator.Next();
                        }
                        primaryTable.ForeignKeys.Add(newRelationship);
                    }
                }
            }
            return result;
        }

    }
}
